/*-------------------------------------------------------------------------
 *
 * autoprewarm.c
 *		定期转储有关共享缓冲区中存在的块的信息，并在服务器重启时重新加载它们。
 *
 *		由于锁定考虑，我们实际上不能开始预热，
 *		直到服务器达到一致状态。我们需要目录
 *		保持一致，以便能够确定需要锁定哪种关系，
 *		并且我们需要锁定这些关系，以便不尝试预热
 *		正在被删除的关系的页面。
 *
 *		在预热期间，autoprewarm 将使用两个工作线程。存在一个
 *		负责读取和排序待预热块列表的领导工作线程，
 *		然后依次为每个相关数据库启动一个数据库特定的工作线程。前者在
 *		初始预热完成后继续运行，以定期更新转储文件。
 *
 *	Copyright (c) 2016-2022, PostgreSQL Global Development Group
 *
 *	IDENTIFICATION
 *		contrib/pg_prewarm/autoprewarm.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <unistd.h>

#include "access/relation.h"
#include "access/xact.h"
#include "catalog/pg_class.h"
#include "catalog/pg_type.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/bgworker.h"
#include "postmaster/interrupt.h"
#include "storage/buf_internals.h"
#include "storage/dsm.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/lwlock.h"
#include "storage/proc.h"
#include "storage/procsignal.h"
#include "storage/shmem.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/acl.h"
#include "utils/datetime.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/relfilenodemap.h"
#include "utils/resowner.h"

#define AUTOPREWARM_FILE "autoprewarm.blocks"

/* 每个我们转储的块的元数据。 */
typedef struct BlockInfoRecord
{
	Oid			database;
	Oid			tablespace;
	Oid			filenode;
	ForkNumber	forknum;
	BlockNumber blocknum;
} BlockInfoRecord;

/* autoprewarm 背景工作者的共享状态信息。 */
typedef struct AutoPrewarmSharedState
{
	LWLock		lock;			/* 互斥 */
	pid_t		bgworker_pid;	/* 主要背景工作者 */
	pid_t		pid_using_dumpfile; /* 用于 autoprewarm 或块转储 */

	/* 以下项用于与每个数据库工作者的通信 */
	dsm_handle	block_info_handle;
	Oid			database;
	int			prewarm_start_idx;
	int			prewarm_stop_idx;
	int			prewarmed_blocks;
} AutoPrewarmSharedState;

void		_PG_init(void);
void		autoprewarm_main(Datum fc_main_arg);
void		autoprewarm_database_main(Datum fc_main_arg);

PG_FUNCTION_INFO_V1(autoprewarm_start_worker);
PG_FUNCTION_INFO_V1(autoprewarm_dump_now);

static void fc_apw_load_buffers(void);
static int	fc_apw_dump_now(bool fc_is_bgworker, bool fc_dump_unlogged);
static void fc_apw_start_leader_worker(void);
static void fc_apw_start_database_worker(void);
static bool fc_apw_init_shmem(void);
static void fc_apw_detach_shmem(int fc_code, Datum fc_arg);
static int	fc_apw_compare_blockinfo(const void *fc_p, const void *fc_q);
static void fc_autoprewarm_shmem_request(void);
static shmem_request_hook_type prev_shmem_request_hook = NULL;

/* 指向共享内存状态的指针。 */
static AutoPrewarmSharedState *apw_state = NULL;

/* GUC 变量。 */
static bool autoprewarm = true; /* 启动工作者？ */
static int	autoprewarm_interval;	/* 转储间隔 */

/*
 * 模块加载回调。
 */
void _PG_init(void)
{
	DefineCustomIntVariable("pg_prewarm.autoprewarm_interval",
							"Sets the interval between dumps of shared buffers",
							"If set to zero, time-based dumping is disabled.",
							&autoprewarm_interval,
							300,
							0, INT_MAX / 1000,
							PGC_SIGHUP,
							GUC_UNIT_S,
							NULL,
							NULL,
							NULL);

	if (!process_shared_preload_libraries_in_progress)
		return;

	/* 启动后无法定义 PGC_POSTMASTER 变量 */
	DefineCustomBoolVariable("pg_prewarm.autoprewarm",
							 "Starts the autoprewarm worker.",
							 NULL,
							 &autoprewarm,
							 true,
							 PGC_POSTMASTER,
							 0,
							 NULL,
							 NULL,
							 NULL);

	MarkGUCPrefixReserved("pg_prewarm");

	prev_shmem_request_hook = shmem_request_hook;
	shmem_request_hook = fc_autoprewarm_shmem_request;

	/* 如果启用，注册 autoprewarm 工作者。 */
	if (autoprewarm)
		fc_apw_start_leader_worker();
}

/*
 * 请求 autoprewarm 需要的任何额外共享内存。
 */
static void fc_autoprewarm_shmem_request(void)
{
	if (prev_shmem_request_hook)
		prev_shmem_request_hook();

	RequestAddinShmemSpace(MAXALIGN(sizeof(AutoPrewarmSharedState)));
}

/*
 * 领导者 autoprewarm 进程的主要入口点。每个数据库工作者
 * 有单独的入口点。
 */
void autoprewarm_main(Datum fc_main_arg)
{
	bool		fc_first_time = true;
	bool		fc_final_dump_allowed = true;
	TimestampTz fc_last_dump_time = 0;

	/* 建立信号处理程序；一旦完成，解除信号屏蔽。 */
	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
	pqsignal(SIGHUP, SignalHandlerForConfigReload);
	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
	BackgroundWorkerUnblockSignals();

	/* 创建（如有必要）并附加到我们的共享内存区域。 */
	if (fc_apw_init_shmem())
		fc_first_time = false;

	/* 设置脱离钩子，以便在退出时清除我们的 PID。 */
	on_shmem_exit(fc_apw_detach_shmem, 0);

	/*
	 * 将我们的 PID 存储在共享内存区域中——除非已经
	 * 有其他工作者在运行，否则直接退出。
	 */
	LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
	if (apw_state->bgworker_pid != InvalidPid)
	{
		LWLockRelease(&apw_state->lock);
		ereport(LOG,
				(errmsg("autoprewarm worker is already running under PID %lu",
						(unsigned long) apw_state->bgworker_pid)));
		return;
	}
	apw_state->bgworker_pid = MyProcPid;
	LWLockRelease(&apw_state->lock);

	/*
	 * 仅当我们刚创建共享内存区域时，从转储文件预加载缓冲区。
	 * 否则，要么已经完成，要么不应该完成——例如，因为
	 * 自服务器启动以来，旧的转储文件已被覆盖。
	 *
	 * 在完成预加载后立即执行转储没有太大意义；因此，如果我们
	 * 确实进行了预加载，请将最后转储时间视为当前时间。
	 *
	 * 如果 apw_load_buffers() 被关闭请求提前终止，
	 * 则在循环下方阻止转储我们的状态，因为我们实际上将
	 * 保存的状态截断为我们成功预加载的内容。
	 */
	if (fc_first_time)
	{
		fc_apw_load_buffers();
		fc_final_dump_allowed = !ShutdownRequestPending;
		fc_last_dump_time = GetCurrentTimestamp();
	}

	/* 定期转储缓冲区，直到终止。 */
	while (!ShutdownRequestPending)
	{
		/* 如果收到 SIGHUP，重新加载配置。 */
		if (ConfigReloadPending)
		{
			ConfigReloadPending = false;
			ProcessConfigFile(PGC_SIGHUP);
		}

		if (autoprewarm_interval <= 0)
		{
			/* 我们只在关闭时进行转储，因此只需永远等待。 */
			(void) WaitLatch(MyLatch,
							 WL_LATCH_SET | WL_EXIT_ON_PM_DEATH,
							 -1L,
							 PG_WAIT_EXTENSION);
		}
		else
		{
			TimestampTz fc_next_dump_time;
			long		fc_delay_in_ms;

			/* 计算下一个转储时间。 */
			fc_next_dump_time =
				TimestampTzPlusMilliseconds(fc_last_dump_time,
											autoprewarm_interval * 1000);
			fc_delay_in_ms =
				TimestampDifferenceMilliseconds(GetCurrentTimestamp(),
												fc_next_dump_time);

			/* 如果到了时间，则执行转储。 */
			if (fc_delay_in_ms <= 0)
			{
				fc_last_dump_time = GetCurrentTimestamp();
				fc_apw_dump_now(true, false);
				continue;
			}

			/* 睡眠，直到下一个转储时间。 */
			(void) WaitLatch(MyLatch,
							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
							 fc_delay_in_ms,
							 PG_WAIT_EXTENSION);
		}

		/* 重置闩锁，循环。 */
		ResetLatch(MyLatch);
	}

	/*
	 * 最后一次转储。我们假设这可能是系统
	 * 关闭的结果，尽管也有可能我们只是被终止。
	 */
	if (fc_final_dump_allowed)
		fc_apw_dump_now(true, true);
}

/*
 * 读取转储文件并逐个启动每个数据库工作者以
 * 预热其中找到的缓冲区。
 */
static void fc_apw_load_buffers(void)
{
	FILE	   *fc_file = NULL;
	int			fc_num_elements,
				fc_i;
	BlockInfoRecord *fc_blkinfo;
	dsm_segment *fc_seg;

	/*
	 * 如果转储文件正在使用，跳过预热；否则，在我们使用它时阻止任何
	 * 其他进程写入它。
	 */
	LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
	if (apw_state->pid_using_dumpfile == InvalidPid)
		apw_state->pid_using_dumpfile = MyProcPid;
	else
	{
		LWLockRelease(&apw_state->lock);
		ereport(LOG,
				(errmsg("skipping prewarm because block dump file is being written by PID %lu",
						(unsigned long) apw_state->pid_using_dumpfile)));
		return;
	}
	LWLockRelease(&apw_state->lock);

	/*
	 * 打开块转储文件。如果文件不存在，则静默退出，但报告
	 * 任何其他错误。
	 */
	fc_file = AllocateFile(AUTOPREWARM_FILE, "r");
	if (!fc_file)
	{
		if (errno == ENOENT)
		{
			LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
			apw_state->pid_using_dumpfile = InvalidPid;
			LWLockRelease(&apw_state->lock);
			return;				/* 没有文件可加载。 */
		}
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not read file \"%s\": %m",
						AUTOPREWARM_FILE)));
	}

	/* 文件的第一行是记录计数。 */
	if (fscanf(fc_file, "<<%d>>\n", &fc_num_elements) != 1)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not read from file \"%s\": %m",
						AUTOPREWARM_FILE)));

	/* 分配一个动态共享内存段以存储记录数据。 */
	fc_seg = dsm_create(sizeof(BlockInfoRecord) * fc_num_elements, 0);
	fc_blkinfo = (BlockInfoRecord *) dsm_segment_address(fc_seg);

	/* 逐行读取记录。 */
	for (fc_i = 0; fc_i < fc_num_elements; fc_i++)
	{
		unsigned	fc_forknum;

		if (fscanf(fc_file, "%u,%u,%u,%u,%u\n", &fc_blkinfo[fc_i].database,
				   &fc_blkinfo[fc_i].tablespace, &fc_blkinfo[fc_i].filenode,
				   &fc_forknum, &fc_blkinfo[fc_i].blocknum) != 5)
			ereport(ERROR,
					(errmsg("autoprewarm block dump file is corrupted at line %d",
							fc_i + 1)));
		fc_blkinfo[fc_i].forknum = fc_forknum;
	}

	FreeFile(fc_file);

	/* 对要加载的块进行排序。 */
	pg_qsort(fc_blkinfo, fc_num_elements, sizeof(BlockInfoRecord),
			 fc_apw_compare_blockinfo);

	/* 填充共享内存状态。 */
	apw_state->block_info_handle = dsm_segment_handle(fc_seg);
	apw_state->prewarm_start_idx = apw_state->prewarm_stop_idx = 0;
	apw_state->prewarmed_blocks = 0;

	/* 获取下一个数据库的第一个块的信息位置。 */
	while (apw_state->prewarm_start_idx < fc_num_elements)
	{
		int			fc_j = apw_state->prewarm_start_idx;
		Oid			fc_current_db = fc_blkinfo[fc_j].database;

		/*
		 * 将prewarm_stop_idx推进到第一个不属于此数据库的BlockInfoRecord。
		 */
		fc_j++;
		while (fc_j < fc_num_elements)
		{
			if (fc_current_db != fc_blkinfo[fc_j].database)
			{
				/*
				 * 将全局对象的BlockInfoRecords与数据库的BlockInfoRecords合并。
				 */
				if (fc_current_db != InvalidOid)
					break;
				fc_current_db = fc_blkinfo[fc_j].database;
			}

			fc_j++;
		}

		/*
		 * 如果我们在current_db == InvalidOid的情况下到达此点，则只存在属于全局对象的BlockInfoRecords。
		 * 我们无法在没有数据库连接的情况下进行预热，因此直接退出。
		 */
		if (fc_current_db == InvalidOid)
			break;

		/* 为下一个每数据库的工作者配置停止点和数据库。 */
		apw_state->prewarm_stop_idx = fc_j;
		apw_state->database = fc_current_db;
		Assert(apw_state->prewarm_start_idx < apw_state->prewarm_stop_idx);

		/* 如果我们用完了空闲缓冲区，请勿启动另一个工作者。 */
		if (!have_free_buffer())
			break;

		/*
		 * 同样，如果我们已经被告知关闭，则不要启动。
		 * （启动无论如何都会失败，但我们还是可以跳过它。）
		 */
		if (ShutdownRequestPending)
			break;

		/*
		 * 启动一个每数据库的工作者以加载此数据库的块；此
		 * 函数将在每数据库的工作者退出后返回。
		 */
		fc_apw_start_database_worker();

		/* 为下一个数据库做准备。 */
		apw_state->prewarm_start_idx = apw_state->prewarm_stop_idx;
	}

	/* 清理。 */
	dsm_detach(fc_seg);
	LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
	apw_state->block_info_handle = DSM_HANDLE_INVALID;
	apw_state->pid_using_dumpfile = InvalidPid;
	LWLockRelease(&apw_state->lock);

	/* 报告我们的成功，如果我们能够完成的话。 */
	if (!ShutdownRequestPending)
		ereport(LOG,
				(errmsg("autoprewarm successfully prewarmed %d of %d previously-loaded blocks",
						apw_state->prewarmed_blocks, fc_num_elements)));
}

/*
 * 为一个数据库（如果全局对象与此数据库分组，也可能包括全局对象）预热所有块。
 */
void autoprewarm_database_main(Datum fc_main_arg)
{
	int			fc_pos;
	BlockInfoRecord *fc_block_info;
	Relation	fc_rel = NULL;
	BlockNumber fc_nblocks = 0;
	BlockInfoRecord *fc_old_blk = NULL;
	dsm_segment *fc_seg;

	/* 建立信号处理程序；一旦完成，解除信号屏蔽。 */
	pqsignal(SIGTERM, die);
	BackgroundWorkerUnblockSignals();

	/* 连接到正确的数据库并获取块信息。 */
	fc_apw_init_shmem();
	fc_seg = dsm_attach(apw_state->block_info_handle);
	if (fc_seg == NULL)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("could not map dynamic shared memory segment")));
	BackgroundWorkerInitializeConnectionByOid(apw_state->database, InvalidOid, 0);
	fc_block_info = (BlockInfoRecord *) dsm_segment_address(fc_seg);
	fc_pos = apw_state->prewarm_start_idx;

	/*
	 * 循环直到我们用完要预热的块或者用完空闲缓冲区。
	 */
	while (fc_pos < apw_state->prewarm_stop_idx && have_free_buffer())
	{
		BlockInfoRecord *fc_blk = &fc_block_info[fc_pos++];
		Buffer		fc_buf;

		CHECK_FOR_INTERRUPTS();

		/*
		 * 如果我们到达另一个数据库的记录，则退出。如果先前的块是某些全局对象，则继续预热。
		 */
		if (fc_old_blk != NULL && fc_old_blk->database != fc_blk->database &&
			fc_old_blk->database != 0)
			break;

		/*
		 * 一旦遇到新关系的块，关闭旧关系。注意，如果try_relation_open先前失败，
		 * rel将为NULL；在这种情况下，没有什么可以关闭。
		 */
		if (fc_old_blk != NULL && fc_old_blk->filenode != fc_blk->filenode &&
			fc_rel != NULL)
		{
			relation_close(fc_rel, AccessShareLock);
			fc_rel = NULL;
			CommitTransactionCommand();
		}

		/*
		 * 尝试打开每个新关系，但只在第一次遇到时尝试。如果它已被删除，则跳过相关块。
		 */
		if (fc_old_blk == NULL || fc_old_blk->filenode != fc_blk->filenode)
		{
			Oid			fc_reloid;

			Assert(fc_rel == NULL);
			StartTransactionCommand();
			fc_reloid = RelidByRelfilenode(fc_blk->tablespace, fc_blk->filenode);
			if (OidIsValid(fc_reloid))
				fc_rel = try_relation_open(fc_reloid, AccessShareLock);

			if (!fc_rel)
				CommitTransactionCommand();
		}
		if (!fc_rel)
		{
			fc_old_blk = fc_blk;
			continue;
		}

		/* 每次分叉时，检查分叉的存在和大小。 */
		if (fc_old_blk == NULL ||
			fc_old_blk->filenode != fc_blk->filenode ||
			fc_old_blk->forknum != fc_blk->forknum)
		{
			/*
			 * smgrexists对于非法的forknum是不安全的，因此在使用它之前检查传递的forknum是否有效。
			 */
			if (fc_blk->forknum > InvalidForkNumber &&
				fc_blk->forknum <= MAX_FORKNUM &&
				smgrexists(RelationGetSmgr(fc_rel), fc_blk->forknum))
				fc_nblocks = RelationGetNumberOfBlocksInFork(fc_rel, fc_blk->forknum);
			else
				fc_nblocks = 0;
		}

		/* 检查blocknum是否有效并在fork文件大小范围内。 */
		if (fc_blk->blocknum >= fc_nblocks)
		{
			/* 移动到下一个forknum。 */
			fc_old_blk = fc_blk;
			continue;
		}

		/* 预热缓冲区。 */
		fc_buf = ReadBufferExtended(fc_rel, fc_blk->forknum, fc_blk->blocknum, RBM_NORMAL,
								 NULL);
		if (BufferIsValid(fc_buf))
		{
			apw_state->prewarmed_blocks++;
			ReleaseBuffer(fc_buf);
		}

		fc_old_blk = fc_blk;
	}

	dsm_detach(fc_seg);

	/* 释放对先前关系的锁定。 */
	if (fc_rel)
	{
		relation_close(fc_rel, AccessShareLock);
		CommitTransactionCommand();
	}
}

/*
 * 转储共享缓冲区中块的信息。我们在这里使用文本格式
 * 以便于理解，甚至在必要时更改文件内容。
 * 返回转储的块的数量。
 */
static int fc_apw_dump_now(bool fc_is_bgworker, bool fc_dump_unlogged)
{
	int			fc_num_blocks;
	int			fc_i;
	int			fc_ret;
	BlockInfoRecord *fc_block_info_array;
	BufferDesc *fc_bufHdr;
	FILE	   *fc_file;
	char		fc_transient_dump_file_path[MAXPGPATH];
	pid_t		fc_pid;

	LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
	fc_pid = apw_state->pid_using_dumpfile;
	if (apw_state->pid_using_dumpfile == InvalidPid)
		apw_state->pid_using_dumpfile = MyProcPid;
	LWLockRelease(&apw_state->lock);

	if (fc_pid != InvalidPid)
	{
		if (!fc_is_bgworker)
			ereport(ERROR,
					(errmsg("could not perform block dump because dump file is being used by PID %lu",
							(unsigned long) apw_state->pid_using_dumpfile)));

		ereport(LOG,
				(errmsg("skipping block dump because it is already being performed by PID %lu",
						(unsigned long) apw_state->pid_using_dumpfile)));
		return 0;
	}

	fc_block_info_array =
		(BlockInfoRecord *) palloc(sizeof(BlockInfoRecord) * NBuffers);

	for (fc_num_blocks = 0, fc_i = 0; fc_i < NBuffers; fc_i++)
	{
		uint32		fc_buf_state;

		CHECK_FOR_INTERRUPTS();

		fc_bufHdr = GetBufferDescriptor(fc_i);

		/* 在检查之前锁定每个缓冲区头。 */
		fc_buf_state = LockBufHdr(fc_bufHdr);

		/*
		 * 未记录的表将在崩溃或不干净的关闭后自动截断。
		 * 在这种情况下，我们无需预热它们。仅在调用者请求时转储它们。
		 */
		if (fc_buf_state & BM_TAG_VALID &&
			((fc_buf_state & BM_PERMANENT) || fc_dump_unlogged))
		{
			fc_block_info_array[fc_num_blocks].database = fc_bufHdr->tag.rnode.dbNode;
			fc_block_info_array[fc_num_blocks].tablespace = fc_bufHdr->tag.rnode.spcNode;
			fc_block_info_array[fc_num_blocks].filenode = fc_bufHdr->tag.rnode.relNode;
			fc_block_info_array[fc_num_blocks].forknum = fc_bufHdr->tag.forkNum;
			fc_block_info_array[fc_num_blocks].blocknum = fc_bufHdr->tag.blockNum;
			++fc_num_blocks;
		}

		UnlockBufHdr(fc_bufHdr, fc_buf_state);
	}

	snprintf(fc_transient_dump_file_path, MAXPGPATH, "%s.tmp", AUTOPREWARM_FILE);
	fc_file = AllocateFile(fc_transient_dump_file_path, "w");
	if (!fc_file)
#ifdef FDD //cppcheck
	{
#endif
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not open file \"%s\": %m",
						fc_transient_dump_file_path)));
#ifdef FDD //cppcheck
		return 0;
	}
#endif
	fc_ret = fprintf(fc_file, "<<%d>>\n", fc_num_blocks);
	if (fc_ret < 0)
	{
		int			fc_save_errno = errno;

		FreeFile(fc_file);
		unlink(fc_transient_dump_file_path);
		errno = fc_save_errno;
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not write to file \"%s\": %m",
						fc_transient_dump_file_path)));
	}

	for (fc_i = 0; fc_i < fc_num_blocks; fc_i++)
	{
		CHECK_FOR_INTERRUPTS();

		fc_ret = fprintf(fc_file, "%u,%u,%u,%u,%u\n",
					  fc_block_info_array[fc_i].database,
					  fc_block_info_array[fc_i].tablespace,
					  fc_block_info_array[fc_i].filenode,
					  (uint32) fc_block_info_array[fc_i].forknum,
					  fc_block_info_array[fc_i].blocknum);
		if (fc_ret < 0)
		{
			int			fc_save_errno = errno;

			FreeFile(fc_file);
			unlink(fc_transient_dump_file_path);
			errno = fc_save_errno;
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not write to file \"%s\": %m",
							fc_transient_dump_file_path)));
		}
	}

	pfree(fc_block_info_array);

	/*
	 * 将transient_dump_file_path重命名为AUTOPREWARM_FILE以使其永久化。
	 */
	fc_ret = FreeFile(fc_file);
	if (fc_ret != 0)
	{
		int			fc_save_errno = errno;

		unlink(fc_transient_dump_file_path);
		errno = fc_save_errno;
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not close file \"%s\": %m",
						fc_transient_dump_file_path)));
	}

	(void) durable_rename(fc_transient_dump_file_path, AUTOPREWARM_FILE, ERROR);
	apw_state->pid_using_dumpfile = InvalidPid;

	ereport(DEBUG1,
			(errmsg_internal("wrote block details for %d blocks", fc_num_blocks)));
	return fc_num_blocks;
}

/*
 * 可供SQL调用的函数以启动autoprewarm。
 */
Datum autoprewarm_start_worker(PG_FUNCTION_ARGS)
{
	pid_t		fc_pid;

	if (!autoprewarm)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("autoprewarm is disabled")));

	fc_apw_init_shmem();
	LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
	fc_pid = apw_state->bgworker_pid;
	LWLockRelease(&apw_state->lock);

	if (fc_pid != InvalidPid)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("autoprewarm worker is already running under PID %lu",
						(unsigned long) fc_pid)));

	fc_apw_start_leader_worker();

	PG_RETURN_VOID();
}

/*
 * SQL-callable function to perform an immediate block dump.
 *
 * 注意：这被声明返回 int8，以防在某个遥远的日子里我们可能会让 NBuffers 的宽度超过 int。
 */
Datum autoprewarm_dump_now(PG_FUNCTION_ARGS)
{
	int			fc_num_blocks;

	fc_apw_init_shmem();

	PG_ENSURE_ERROR_CLEANUP(fc_apw_detach_shmem, 0);
	{
		fc_num_blocks = fc_apw_dump_now(false, true);
	}
	PG_END_ENSURE_ERROR_CLEANUP(fc_apw_detach_shmem, 0);

	PG_RETURN_INT64((int64) fc_num_blocks);
}

/*
 * 分配并初始化与 autoprewarm 相关的共享内存（如果尚未完成），
 * 并设置后端本地指针指向该状态。 如果找到现有的共享内存段，则返回 true。
 */
static bool fc_apw_init_shmem(void)
{
	bool		fc_found;

	LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
	apw_state = ShmemInitStruct("autoprewarm",
								sizeof(AutoPrewarmSharedState),
								&fc_found);
	if (!fc_found)
	{
		/* 第一次通过 ... */
		LWLockInitialize(&apw_state->lock, LWLockNewTrancheId());
		apw_state->bgworker_pid = InvalidPid;
		apw_state->pid_using_dumpfile = InvalidPid;
	}
	LWLockRelease(AddinShmemInitLock);

	LWLockRegisterTranche(apw_state->lock.tranche, "autoprewarm");

	return fc_found;
}

/*
 * 从 autoprewarm 共享状态中清除我们的 PID。
 */
static void fc_apw_detach_shmem(int fc_code, Datum fc_arg)
{
	LWLockAcquire(&apw_state->lock, LW_EXCLUSIVE);
	if (apw_state->pid_using_dumpfile == MyProcPid)
		apw_state->pid_using_dumpfile = InvalidPid;
	if (apw_state->bgworker_pid == MyProcPid)
		apw_state->bgworker_pid = InvalidPid;
	LWLockRelease(&apw_state->lock);
}

/*
 * 启动 autoprewarm 领导工作进程。
 */
static void fc_apw_start_leader_worker(void)
{
	BackgroundWorker fc_worker;
	BackgroundWorkerHandle *fc_handle;
	BgwHandleStatus fc_status;
	pid_t		fc_pid;

	MemSet(&fc_worker, 0, sizeof(BackgroundWorker));
	fc_worker.bgw_flags = BGWORKER_SHMEM_ACCESS;
	fc_worker.bgw_start_time = BgWorkerStart_ConsistentState;
	strcpy(fc_worker.bgw_library_name, "pg_prewarm");
	strcpy(fc_worker.bgw_function_name, "autoprewarm_main");
	strcpy(fc_worker.bgw_name, "autoprewarm leader");
	strcpy(fc_worker.bgw_type, "autoprewarm leader");

	if (process_shared_preload_libraries_in_progress)
	{
		RegisterBackgroundWorker(&fc_worker);
		return;
	}

	/* 必须设置通知 PID 以等待启动 */
	fc_worker.bgw_notify_pid = MyProcPid;

	if (!RegisterDynamicBackgroundWorker(&fc_worker, &fc_handle))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("could not register background process"),
				 errhint("You may need to increase max_worker_processes.")));

	fc_status = WaitForBackgroundWorkerStartup(fc_handle, &fc_pid);
	if (fc_status != BGWH_STARTED)
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("could not start background process"),
				 errhint("More details may be available in the server log.")));
}

/*
 * 启动每个数据库的 autoprewarm 工作进程。
 */
static void fc_apw_start_database_worker(void)
{
	BackgroundWorker fc_worker;
	BackgroundWorkerHandle *fc_handle;

	MemSet(&fc_worker, 0, sizeof(BackgroundWorker));
	fc_worker.bgw_flags =
		BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION;
	fc_worker.bgw_start_time = BgWorkerStart_ConsistentState;
	fc_worker.bgw_restart_time = BGW_NEVER_RESTART;
	strcpy(fc_worker.bgw_library_name, "pg_prewarm");
	strcpy(fc_worker.bgw_function_name, "autoprewarm_database_main");
	strcpy(fc_worker.bgw_name, "autoprewarm worker");
	strcpy(fc_worker.bgw_type, "autoprewarm worker");

	/* 必须设置通知 PID 以等待关闭 */
	fc_worker.bgw_notify_pid = MyProcPid;

	if (!RegisterDynamicBackgroundWorker(&fc_worker, &fc_handle))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
				 errmsg("registering dynamic bgworker autoprewarm failed"),
				 errhint("Consider increasing configuration parameter \"max_worker_processes\".")));

	/*
	 * 忽略返回值；如果失败，postmaster 已经死亡，但我们在其他地方有
	 * 针对这一情况的检查。
	 */
	WaitForBackgroundWorkerShutdown(fc_handle);
}

/* 比较成员元素以检查它们是否不相等。 */
#define cmp_member_elem(fld)	\
do { \
	if (fc_a->fld < fc_b->fld)		\
		return -1;				\
	else if (fc_a->fld > fc_b->fld)	\
		return 1;				\
} while(0)

/*
 * apw_compare_blockinfo
 *
 * 我们依赖于特定数据库的所有记录在转储文件中是连续的；
 * 每个数据库的工作进程将预加载块，直到看到其他数据库的块为止。
 * 按表空间、文件节点、分叉号和块号排序对正确性并不关键，
 * 但有助于我们获得顺序 I/O 模式。
 */
static int fc_apw_compare_blockinfo(const void *fc_p, const void *fc_q)
{
	const BlockInfoRecord *fc_a = (const BlockInfoRecord *) fc_p;
	const BlockInfoRecord *fc_b = (const BlockInfoRecord *) fc_q;

	cmp_member_elem(database);
	cmp_member_elem(tablespace);
	cmp_member_elem(filenode);
	cmp_member_elem(forknum);
	cmp_member_elem(blocknum);

	return 0;
}
